#include "common.h"
#include "counter.h"
#include "Vcompress_issue_queue_test.h"

#define TRACE 0
#define MAX_WAVE 1000000
#define MAX_CYCLE 1000000
Vcompress_issue_queue_test *top;
VerilatedFstC *tfp;
#define RF_WRITE_WIDTH 5
#define ENQ_WIDTH 4
#define DEQ_WIDTH 1
#define SIZE 16
#define PRF_SIZE 92
Counter cycle("cycle");
Counter total_op("total_op");
Counter op_cnt("op_cnt");
bool percent(int change)
{
    uint32_t r = rand();
    r %= 100;
    return r < change;
}
uint32_t rand_n(uint32_t n)
{
    uint32_t r = rand();
    return r % n;
}
void dump()
{
#if TRACE
    if (Verilated::time() < MAX_WAVE)
    {
        tfp->dump(Verilated::time());
    }
#endif
}
void run()
{
    top->eval();
    dump();
    Verilated::timeInc(1);

    top->clk = 0;
    top->eval();
    dump();
    Verilated::timeInc(2);

    top->clk = 1;
    top->eval();
    dump();
    Verilated::timeInc(1);
}
void clear_all()
{
    for (size_t i = 0; i < RF_WRITE_WIDTH; i++)
    {
        top->i_write_valid[i] = 0;
    }
    for (size_t i = 0; i < ENQ_WIDTH; i++)
    {
        top->i_valid[i] = 0;
    }
    top->o_ready = 0;
}
void reset()
{
    clear_all();

    top->rst = 1;
    run();
    run();
    run();
    top->rst = 0;
}
#include <queue>
bool busy_table[PRF_SIZE] = {0};
std::queue<uint32_t> wake_queue;
void wake_reg(uint32_t wake_num)
{
    for (size_t i = 0; i < wake_num && i < RF_WRITE_WIDTH; i++)
    {
        if (wake_queue.empty())
            break;
        busy_table[wake_queue.front()] = false;
        top->i_write_valid[i] = 1;
        top->i_write_prf_data[i] = wake_queue.front();
        top->i_write_prf_idx[i] = wake_queue.front();
        wake_queue.pop();
    }
}
void rand_clear_table(uint32_t clear_num)
{
    for (size_t i = 0; i < clear_num; i++)
    {
        auto r = rand_n(PRF_SIZE);
        if (!busy_table[r])
        {
            busy_table[r] = true;
            wake_queue.push(r);
        }
    }
}
void make_rand_input()
{
    uint32_t num_enq = rand_n(ENQ_WIDTH + 1);
    // LOG("make_rand_input %d at %ld", num_enq,Verilated::time());
    static int rob_idx = 0;
    static word_t src = 0;
    for (size_t i = 0; i < num_enq; i++)
    {
        top->i_iop_rob_idx[i] = rob_idx++;
        top->i_iop_prs1[i] = rand_n(PRF_SIZE);
        top->i_iop_prs2[i] = rand_n(PRF_SIZE);
        top->i_iop_src1[i] = top->i_iop_prs1[i];
        top->i_iop_src2[i] = top->i_iop_prs2[i];
        top->i_iop_src1_valid[i] = !busy_table[top->i_iop_prs1[i]];
        top->i_iop_src2_valid[i] = !busy_table[top->i_iop_prs2[i]];
        top->i_valid[i] = 1;
        total_op++;
        // LOG("Make[%ld] : %d Src1_valid %d Src2_valid %d", i, top->i_iop_rob_idx[i], top->i_iop_src1_valid[i], top->i_iop_src2_valid[i]);
    }
}
void clear_all_fired()
{
    for (size_t i = 0; i < RF_WRITE_WIDTH; i++)
    {
        top->i_write_valid[i] = 0;
    }
    if (top->i_ready)
    {
        for (size_t i = 0; i < ENQ_WIDTH; i++)
        {
            top->i_valid[i] = 0;
        }
    }
}

void compress_issue_queue_test()
{
    LOG("compress_issue_queue_test");

    reset();
    do
    {
        wake_reg(rand_n(RF_WRITE_WIDTH + 1));
        rand_clear_table(rand_n(10));
        if (top->o_ready)
        {
            for (size_t i = 0; i < DEQ_WIDTH; i++)
            {
                if (top->o_valid[i])
                {
                    op_cnt++;
                }
            }
        }
        run();
        cycle++;
        clear_all_fired();
        if (!top->i_valid[0])
        {
            make_rand_input();
        }
        top->o_ready = percent(70);
    } while ((!Verilated::gotFinish()) && cycle < MAX_CYCLE);
}

int main()
{
    srand(0);
    Verilated::randSeed(0);
    Verilated::randReset(1);
    top = new Vcompress_issue_queue_test();
#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top->trace(tfp, 0);
    tfp->open("Vcompress_issue_queue_test.fst");
#endif
    reset();
    // Run all tests
    compress_issue_queue_test();
    // Final model cleanup
    run();
    top->final();
#if TRACE
    tfp->close();
    delete tfp;
#endif
    delete top;
    Counter::print_counters();
    LOG("All test done");
    return 0;
}